package connezione;

/**
 *
 * @author sestari
 */
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * 
 * Rapresenta il pool dei connezioni. Esso istanzia le connezioni
 * 
 */
public class ConnectionPool {

    private int initialCapacity;
    private int maxCapacity;
    private int step;
    private String connectionString;
    private String driver;
    private Properties properties;
    private long timeOut;
    private List<PooledConnection> unavailable, available;

    private ConnectionPool(String driver) throws ClassNotFoundException {
        this.driver = driver;
        Class.forName(driver);
        this.unavailable = new ArrayList<PooledConnection>();
        this.available = new ArrayList<PooledConnection>();
    }

    private void init() {
        //Inizializa il Pool
        for (int i = 0; i < this.getInitialCapacity(); i++) {
            PooledConnection conn = this.createConnection();
            this.available.add(conn);
        }
    }

    public void setInitialCapacity(int initialCapacity) {
        this.initialCapacity = initialCapacity;
    }

    public int getInitialCapacity() {
        return initialCapacity;
    }

    public void setMaxCapacity(int maxCapacity) {
        this.maxCapacity = maxCapacity;
    }

    public int getMaxCapacity() {
        return maxCapacity;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public int getStep() {
        return step;
    }

    public void setConnectionString(String connectionString) {
        this.connectionString = connectionString;
    }

    public String getConnectionString() {
        return connectionString;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getDriver() {
        return driver;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public Properties getProperties() {
        return properties;
    }

    public synchronized void checkIn(PooledConnection conn) {
        PooledConnection pconn = (PooledConnection) conn;
        this.available.add(conn);
        this.unavailable.remove(conn);



        this.notifyAll();
    }

    public synchronized PooledConnection checkOut() {
        if (this.available.size() > 0) {
            for (PooledConnection conn : this.available) {
                //Connezione Valida
                if (this.isValid(conn)) {
                    this.available.remove(conn);
                    this.unavailable.add(conn);
                    System.out.println("disponibilizou conex�o: " + conn);
                    return conn;
                } else {
                    this.expire(conn);
                    continue;
                }
            }
        } else {
            //Verifica se si puo creare nuove connezioni
            if (this.creatorManager()) {
                return this.checkOut();
            } else {
                //Se non ne ha creato aspetta				
                try {
                    System.out.println("Thread deve aspetare " + Thread.currentThread().getId());
                    wait();
                    //quando si sveglia prende la connezione
                    return this.checkOut();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public int totalConnections() {
        return this.available.size() + this.unavailable.size();
    }

    public int availableConnections() {
        return this.available.size();
    }

    public int unavailableConnections() {
        return this.unavailable.size();
    }

    private boolean isValid(Connection conn) {
        //verifica timeout
        PooledConnection pconn = (PooledConnection) conn;
        long connTime = pconn.getCreationTime();
        long now = System.currentTimeMillis();
        if (this.timeOut > 0 && (now - connTime) > this.timeOut) {
            return false;
        }

        //verifica se la connezione eh aberta
        try {
            if (conn.isClosed()) {
                return false;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return true;
    }

    private void expire(PooledConnection conn) {
        PooledConnection pconn = conn;
        try {
            this.available.remove(conn);
            this.unavailable.remove(conn);
            if (!pconn.isClosed()) {
                pconn.reallyClose();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private PooledConnection createConnection() {
        try {
            //apre la connezione
            Connection conn = DriverManager.getConnection(this.connectionString, this.properties);
            PooledConnection pconn = new PooledConnection(conn, this);
            return pconn;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    private boolean creatorManager() {
        if (this.availableConnections() == 0 && this.totalConnections() < this.getMaxCapacity()) {
            for (int i = 0; i < this.getStep(); i++) {
                PooledConnection conn = this.createConnection();
                this.available.add(conn);
                System.out.println("CRIOU connection " + conn);
            }
            return true;
        }
        return false;
    }

    public void setTimeOut(long miliSeconds) {
        this.timeOut = miliSeconds;
    }

    public long getTimeOut() {
        return timeOut;
    }

    public synchronized void releaseAll() {

        Iterator<PooledConnection> it = this.available.iterator();
        while (it.hasNext()) {
            PooledConnection conn = it.next();
            it.remove();
            PooledConnection pconn = conn;
            try {
                pconn.reallyClose();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


        it = this.unavailable.iterator();
        while (it.hasNext()) {
            PooledConnection conn = it.next();
            it.remove();
            PooledConnection pconn = conn;
            try {
                pconn.reallyClose();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Builder {

        private int initialCapacity;
        private int maxCapacity;
        private int step;
        private String connectionString;
        private String driver;
        private long timeOut;
        private Properties properties;

        public Builder setInitialCapacity(int initCapacity) {
            this.initialCapacity = initCapacity;
            return this;
        }

        public Builder setMaxCapacity(int maxCapacity) {
            this.maxCapacity = maxCapacity;
            return this;
        }

        public Builder setStep(int step) {
            this.step = step;
            return this;
        }

        public Builder setConnectionString(String cstr) {
            this.connectionString = cstr;
            return this;
        }

        public Builder setDriver(String driver) {
            this.driver = driver;
            return this;
        }

        public Builder setProperties(Properties prop) {
            this.properties = prop;
            return this;
        }

        public Builder setTimeOut(long miliSeconds) {
            this.timeOut = miliSeconds;
            return this;
        }

        public ConnectionPool build() throws ClassNotFoundException {
            ConnectionPool pool = null;
            try {
                pool = new ConnectionPool(this.driver);
                pool.setInitialCapacity(this.initialCapacity);
                pool.setMaxCapacity(this.maxCapacity);
                pool.setStep(this.step);
                pool.setConnectionString(this.connectionString);
                pool.setProperties(this.properties);
                pool.setTimeOut(this.timeOut);

                pool.init();
            } catch (ClassNotFoundException e) {
                throw new ClassNotFoundException("senza Driver Connezione ");
            }

            return pool;
        }
    }
}
